package md.frolov.bible.converters;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

/**
 * This converter converts from BibleQuote format to XML format
 * 
 * @author Ivan Frolov, ivan@frolov.md
 * 
 */
public class BQ2XMLConverter {
	// private static String pathIni="/home/gusto/Desktop/kjv/bibleqt.ini";
	private static String wPath = "/home/gusto/Eclipse/BQ-books/";
	private static String outputPath = "/home/gusto/Eclipse/TB-books/";
	private static String encoding = "Cp1251";

	private static String chapterSign;
	private static String verseSign;
	private static boolean strong = false;
	private static final String modulePrefix = "bible";
	private static String shortName;
	private static String language;
	private static boolean hasOT = false;

	private static Properties props;

	private static Document dom;
	private static Element rootElement;

	private static int bookCounter = 0;
	private static long beginTime;
	
	private static boolean removeStrong = true;

	public final static String[] bookShortNamesEn = { "gen", "exo", "lev",
			"num", "deu", "jos", "jdg", "rut", "1sa", "2sa", "1ki", "2ki",
			"1cr", "2cr", "ezr", "neh", "est", "job", "psa", "pro", "ecl",
			"son", "isa", "jer", "lam", "eze", "dan", "hos", "joe", "amo",
			"oba", "jon", "mic", "nau", "hab", "zep", "hag", "zec", "mal",
			"mat", "mar", "luk", "jhn", "act", "rom", "1co", "2co", "gal",
			"eph", "php", "col", "1th", "2th", "1ti", "2ti", "tit", "phm",
			"heb", "jms", "1pe", "2pe", "1jn", "2jn", "3jn", "jud", "rev" };
	// the difference is only in order
	public final static String[] bookShortNamesRu = { "gen", "exo", "lev",
			"num", "deu", "jos", "jdg", "rut", "1sa", "2sa", "1ki", "2ki",
			"1cr", "2cr", "ezr", "neh", "est", "job", "psa", "pro", "ecl",
			"son", "isa", "jer", "lam", "eze", "dan", "hos", "joe", "amo",
			"oba", "jon", "mic", "nau", "hab", "zep", "hag", "zec", "mal",
			"mat", "mar", "luk", "jhn", "act", "jms", "1pe", "2pe", "1jn",
			"2jn", "3jn", "jud", "rom", "1co", "2co", "gal", "eph", "php",
			"col", "1th", "2th", "1ti", "2ti", "tit", "phm", "heb", "rev",
			"1ma", "2ma", "3ma", // Макавейские
			"bar", // варух
			"2ez", "3ez", // Ездры
			"jdp", // Иудифь
			"eje", // послание Иеремии
			"prs", // Премудрости соломона
			"sir", // Сирах
			"tob" // Товит
	};
	
	public final static int newTestamentIx = 39;
	public final static int apocrIx = 66;
	
	public final static String[] bookFileNamesEn = { "Genesis", "Exodus", "Leviticus",
			"Numbers", "Deuteronomy", "Joshua", "Judges", "Ruth", "1Samuel", "2Samuel", "1Kings", "2Kings",
			"1Chronicles", "2Chronicles", "Ezra", "Nehemiah", "Esther", "Job", "Psalms", "Proverbs", "Ecclesiastes",
			"SongofSolomon", "Isaiah", "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
			"Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai", "Zechariah", "Malachi",
			"Matthew", "Mark", "Luke", "John", "Acts", "Romans", "1Corinthians", "2Corinthians", "Galatians",
			"Ephesians", "Philippians", "Colossians", "1Thessalonians", "2Thessalonians", "1Timothy", "2Timothy", "Titus", "Philemon",
			"Hebrews", "James", "1Peter", "2Peter", "1John", "2John", "3John", "Jude", "Revelation" };

	// the difference is only in order
	public final static String[] bookFileNamesRu = { "Genesis", "Exodus", "Leviticus",
			"Numbers", "Deuteronomy", "Joshua", "Judges", "Ruth", "1Samuel", "2Samuel", "1Kings", "2Kings",
			"1Chronicles", "2Chronicles", "Ezra", "Nehemiah", "Esther", "Job", "Psalms", "Proverbs", "Ecclesiastes",
			"SongofSolomon", "Isaiah", "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
			"Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai", "Zechariah", "Malachi",
			"Matthew", "Mark", "Luke", "John", "Acts", "James", "1Peter", "2Peter", "1John",
			"2John", "3John", "Jude", "Romans", "1Corinthians", "2Corinthians", "Galatians", "Ephesians", "Philippians",
			"Colossians", "1Thessalonians", "2Thessalonians", "1Timothy", "2Timothy", "Titus", "Philemon", "Hebrews", "Revelation",
			"!1ma", "!2ma", "!3ma", // Макавейские
			"!bar", // варух
			"!2ez", "!3ez", // Ездры
			"!jdp", // Иудифь
			"!eje", // послание Иеремии
			"!prs", // Премудрости соломона
			"!sir", // Сирах
			"!tob" // Товит
	};

	public final static int shiftNT = 39;
	public final static int shiftAPO = 66; 

	/**
	 * @param args
	 * @throws IOException
	 * @throws SAXException
	 */
	public static void main(String[] args) throws IOException, SAXException {
		beginTime = System.currentTimeMillis();
		rambleDirectory(new File(wPath));

		beginTime = System.currentTimeMillis() - beginTime;
		System.out.println("Parsing time: " + beginTime / 1000.);
	}

	public static void rambleDirectory(File f) throws IOException, SAXException {
//		if (!f.isDirectory()) return;
		
		File[] list = f.listFiles();
		boolean doReturn = false;
		for (int i = 0; i < list.length; i++) {
			if(list[i].isDirectory()){
				rambleDirectory(list[i]);
				doReturn = true;
			}
			if(list[i].isFile() && list[i].getName().equalsIgnoreCase("bibleqt.ini")){
				parseIniFile(list[i]);
				doReturn = true;
				break;
			}
		}
		if(doReturn) return;
	
		//checking other types
		for (int i = 0; i < list.length; i++) {
			File file = list[i];
			Pattern p = Pattern.compile("(\\w+)\\-(\\w+)\\..*");
			Matcher m = p.matcher(file.getName());
			if(m.matches()){
				String bName = m.group(1);
				for(int j=0;i<bookFileNamesEn.length;j++)
					if(bookFileNamesEn[j].equals(bName)){
						parseType2Directory(f);
						return;
					}
			}
			
		}
	}

	public static void parseType2Directory(File file) throws IOException, SAXException{
		System.out.println("Parseing Type2 directory: "+file.getAbsolutePath());
		
		language = "en";
		
		File[] files = new File[bookFileNamesEn.length];
		File[] list = file.listFiles();
		
		Pattern p = Pattern.compile("(\\w+)\\-(\\w+)\\..*");
		
		for (int i = 0; i < list.length; i++) {
			Matcher m = p.matcher(list[i].getName());
			if(!m.matches()){
				System.out.println("no match: "+file.getName());
				continue;
			}

			boolean yes = false;
			for (int j = 0; j < bookFileNamesEn.length; j++) {
				if(bookFileNamesEn[j].equals(m.group(1))){
					files[j] = list[i];
					yes = true;
					break;
				}
			}
			
			if(yes)
				shortName = m.group(2);
			
			if(!yes){
				System.out.println("Filename doesn't correspond to set of names: "+list[i].getName());
			}
		}
	
		boolean isOT = true;
		boolean isNT = true;
		boolean isApo = bookFileNamesEn.length>apocrIx;
		
		for (int i = 0; i < bookFileNamesEn.length; i++) {
			if(files[i]==null){
				if(i<newTestamentIx) isOT =  false;
				if(i>=newTestamentIx && i<apocrIx) isOT = false;
				if(i>=apocrIx) isApo = false;
			}
		}
	
		chapterSign = "<h3>";
		verseSign = "<p>";
		

		initDocument();

		rootElement.setAttribute("id", "module." + modulePrefix + "." + shortName.toLowerCase());
		rootElement.setAttribute("shortName", shortName);
		rootElement.setAttribute("language", "en");
		rootElement.setAttribute("category", "categories." + modulePrefix);
		
		rootElement.setAttribute("name", file.getName());

		for(int i=0; i<bookFileNamesEn.length;i++){
			if(!isOT && (i>=0 && i<shiftNT)) continue;
			if(!isNT && (i>=shiftNT && i<shiftAPO)) continue;
			if(!isApo && (i>=shiftAPO)) continue;
			
			Element book = dom.createElement("book");
			book.setAttribute("id",modulePrefix+"."+bookShortNamesEn[i]);
			book.setAttribute("name", bookFileNamesEn[i]);
			rootElement.appendChild(book);
			
			parseBook(book, files[i].getAbsolutePath());
		}

		serialize();
	}
	
	public static void parseIniFile(File f) throws IOException, SAXException {
		System.out.println("Parsing file: " + f.getName());
		System.out.println("Working directory: "
				+ f.getParentFile().getAbsolutePath());

		initDocument();
		parseProperties(f, encoding);
		parseBooks(f.getParentFile().getAbsolutePath());
		
		rootElement.setAttribute("id", "module." + modulePrefix + "."
				+ shortName.toLowerCase());
		rootElement.setAttribute("category", "categories." + modulePrefix);

		serialize();
	}

	public static void serialize() throws IOException{
		XMLSerializer xmlSer = new XMLSerializer(new FileWriter(new File(
				outputPath + "/" + shortName.toLowerCase() + ".book"), false),
				new OutputFormat(dom, "UTF-8", true));
		xmlSer.serialize(dom);
	}
	
	public static void parseProperties(File f, String encoding)
			throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(
				new FileInputStream(f), encoding));
		String line;
		while ((line = br.readLine()) != null) {
			// System.out.println(line);
			if (line.startsWith("//"))
				continue;
			if (line.startsWith(";"))
				continue;
			String[] vals = line.split("[ \t]*=[ \t]*", 2);
			// if(vals.length<2)System.out.println("-"+line);
			// if(vals.length==2)System.out.println(vals[0]+"="+vals[1]);
			if (vals.length == 2)
				parseLine(vals[0], vals[1]);
			if (vals.length > 2)
				System.out.println("!" + line);
		}
	}

	public static String getBookId(int bookCounter) {
		StringBuffer sb = new StringBuffer();
		sb.append(modulePrefix).append(".");
		try {
			if (language.equals("ru")) {
				sb.append(bookShortNamesRu[bookCounter + (hasOT ? 0 : shiftNT)
						- 1]);
			} else
				sb.append(bookShortNamesEn[bookCounter + (hasOT ? 0 : shiftNT)
						- 1]);
		} catch (IndexOutOfBoundsException e) {
			sb.append(String.valueOf(bookCounter));
		}
		return sb.toString();
	}

	public static String getVerseId(Element verse, int cCounter, int vCounter) {
		Element chap = (Element) verse.getParentNode();
		String chapId = chap.getAttribute("id");
		if (chapId.startsWith(modulePrefix + ".psa")) {
			if (language.equals("ru")) {
				int vShift = 0;
				int cShift = 0;

				if (cCounter == 9) {
					if (vCounter <= 21) {
						cShift = 0;
						vShift = -1;
					}
					if (vCounter >= 22) {
						cShift = 1;
						vShift = -21;
					}
				}

				if (cCounter == 146) {
					cShift = 1;
					vShift = 0;
				}

				if (cCounter == 147) {
					cShift = 0;
					vShift = 11;
				}

				// смещение на одну главу вперед - главы 9 и 146+147 отличаются
				if ((cCounter >= 10) && (cCounter <= 145))
					cShift = 1;

				// в этих псалмах заголовок внесен в первый стих псалма в
				// русской версии, поэтому смещение -1
				switch (cCounter) {
				case 3:
				case 4:
				case 5:
				case 6:
				case 7:
				case 8:
				case 10:
				case 11:
				case 17:
				case 18:
				case 19:
				case 20:
				case 21:
				case 22:
				case 23:
				case 24:
				case 25:
				case 26:
				case 27:
				case 28:
				case 29:
				case 30:
				case 31:
				case 33:
				case 34:
				case 35:
				case 36:
				case 37:
				case 38:
				case 39:
				case 40:
				case 41:
				case 43:
				case 44:
				case 45:
				case 46:
				case 47:
				case 48:
				case 49:
				case 50:
				case 51:
				case 52:
				case 53:
				case 54:
				case 55:
				case 56:
				case 57:
				case 58:
				case 59:
				case 60:
				case 61:
				case 62:
				case 63:
				case 64:
				case 65:
				case 66:
				case 67:
				case 68:
				case 69:
				case 71:
				case 72:
				case 73:
				case 74:
				case 75:
				case 76:
				case 77:
				case 78:
				case 79:
				case 80:
				case 81:
				case 82:
				case 83:
				case 84:
				case 85:
				case 86:
				case 87:
				case 88:
				case 89:
				case 91:
				case 101:
				case 102:
				case 105:
				case 107:
				case 108:
				case 109:
				case 110:
				case 111:
				case 112:
				case 119:
				case 120:
				case 121:
				case 122:
				case 123:
				case 124:
				case 125:
				case 126:
				case 127:
				case 128:
				case 129:
				case 130:
				case 131:
				case 132:
				case 133:
				case 134:
				case 137:
				case 138:
				case 139:
				case 140:
				case 141:
				case 142:
				case 143:
				case 144:
					vShift = -1;
					break;
				}

				// if(cShift!=0) chap.setAttribute("id",
				// modulePrefix+"."+bookShortNamesEn[18]+"."+cCounter+cShift);
				return modulePrefix + "." + bookShortNamesEn[18] + "."
						+ (cCounter + cShift) + "." + (vCounter + vShift);
			}
			if (language.equals("en")) {

			}
		}
		return chapId + "." + String.valueOf(vCounter);
	}

	public static String getLocalVerseId(Element verse, int cCounter,
			int vCounter) {
		Element chap = (Element) verse.getParentNode();
		String chapId = chap.getAttribute("id");
		if (chapId.startsWith(modulePrefix + ".psa")) {
			if (language.equals("ru")) {
				int vShift = 0;

				if (cCounter == 9) {
					if (vCounter <= 21) {
						vShift = -1;
					}
					if (vCounter >= 22) {
						vShift = -21;
					}
				}

				if (cCounter == 146) {
					vShift = 0;
				}

				if (cCounter == 147) {
					vShift = 11;
				}

				// в этих псалмах заголовок внесен в первый стих псалма в
				// русской версии, поэтому смещение -1
				switch (cCounter) {
				case 3:
				case 4:
				case 5:
				case 6:
				case 7:
				case 8:
				case 10:
				case 11:
				case 17:
				case 18:
				case 19:
				case 20:
				case 21:
				case 22:
				case 23:
				case 24:
				case 25:
				case 26:
				case 27:
				case 28:
				case 29:
				case 30:
				case 31:
				case 33:
				case 34:
				case 35:
				case 36:
				case 37:
				case 38:
				case 39:
				case 40:
				case 41:
				case 43:
				case 44:
				case 45:
				case 46:
				case 47:
				case 48:
				case 49:
				case 50:
				case 51:
				case 52:
				case 53:
				case 54:
				case 55:
				case 56:
				case 57:
				case 58:
				case 59:
				case 60:
				case 61:
				case 62:
				case 63:
				case 64:
				case 65:
				case 66:
				case 67:
				case 68:
				case 69:
				case 71:
				case 72:
				case 73:
				case 74:
				case 75:
				case 76:
				case 77:
				case 78:
				case 79:
				case 80:
				case 81:
				case 82:
				case 83:
				case 84:
				case 85:
				case 86:
				case 87:
				case 88:
				case 89:
				case 91:
				case 101:
				case 102:
				case 105:
				case 107:
				case 108:
				case 109:
				case 110:
				case 111:
				case 112:
				case 119:
				case 120:
				case 121:
				case 122:
				case 123:
				case 124:
				case 125:
				case 126:
				case 127:
				case 128:
				case 129:
				case 130:
				case 131:
				case 132:
				case 133:
				case 134:
				case 137:
				case 138:
				case 139:
				case 140:
				case 141:
				case 142:
				case 143:
				case 144:
					vShift = -1;
					break;
				}

				// if(cShift!=0) chap.setAttribute("id",
				// modulePrefix+"."+bookShortNamesEn[18]+"."+cCounter+cShift);
				return String.valueOf(vCounter + vShift);
			}
			if (language.equals("en")) {

			}
		}
		return String.valueOf(vCounter);
	}

	public static void parseLine(String key, String value) {
		// System.out.println(key+"="+value);

		if (key.equalsIgnoreCase("PathName")) {
			bookCounter++;
			Element book = dom.createElement("book");
			rootElement.appendChild(book);
			book.setAttribute("id", getBookId(bookCounter));
			book.setAttribute("path", value);
			return;
		}

		if (key.equalsIgnoreCase("FullName")) {
			((Element) rootElement.getLastChild()).setAttribute("name", value);
			return;
		}

		if (key.equalsIgnoreCase("ShortName")) {
			if(language.equals("ru")) value = value.replaceAll("[a-zA-Z\\.]+", "");
			String[] shNames = value.trim().split("\\s+");
			Element shName;
			Text text;
			Set<String> namesSet = new HashSet<String>();
			String temp;

			for (int i = 0; i < shNames.length; i++) {
				temp = shNames[i].replaceAll("[\\.\\s]", "");
				if(temp.matches("\\d+"))continue;
				namesSet.add(temp.toLowerCase());
				if(temp.matches("^\\d+.*")){
					temp = temp.replaceAll("(\\d+)", "$1 ");
					namesSet.add(temp.toLowerCase());
				}
			}

			for (Iterator<String> it = namesSet.iterator(); it.hasNext();) {
				String shNameStr = it.next();
				shName = dom.createElement("shortName");
				shName.setAttribute("name", shNameStr);
				((Element) rootElement.getLastChild()).appendChild(shName);
			}
		}

		if (key.equalsIgnoreCase("id")) {
			((Element) rootElement.getLastChild()).setAttribute("id",
					modulePrefix + "." + value.toLowerCase());
		}

		if (key.equalsIgnoreCase("BibleName")) {
			rootElement.setAttribute("name", value);
			return;
		}

		if (key.equalsIgnoreCase("Bible")) {
			rootElement.setAttribute("isBible",
					value.equalsIgnoreCase("y") ? "true" : "false");
			return;
		}

		if (key.equalsIgnoreCase("BibleShortName")) {
			rootElement.setAttribute("shortName", value);
			shortName = value;
			return;
		}

		if (key.equalsIgnoreCase("language")) {
			rootElement.setAttribute("language", value);
			language = value.toLowerCase();
		}

		if (key.equalsIgnoreCase("OldTestament")) {
			rootElement.setAttribute("containsOT",
					value.equalsIgnoreCase("y") ? "true" : "false");
			hasOT = value.equalsIgnoreCase("y") ? true : false;
			return;
		}

		if (key.equalsIgnoreCase("NewTestament")) {
			rootElement.setAttribute("containsNT",
					value.equalsIgnoreCase("y") ? "true" : "false");
			return;
		}

		if (key.equalsIgnoreCase("Apocrypha")) {
			rootElement.setAttribute("containsA",
					value.equalsIgnoreCase("y") ? "true" : "false");
			return;
		}

		if (key.equalsIgnoreCase("StrongNumbers")) {
			rootElement.setAttribute("strongNumbers", value
					.equalsIgnoreCase("y") ? "true" : "false");
			strong = value.equalsIgnoreCase("y") ? true : false;
			return;
		}

		if (key.equalsIgnoreCase("ChapterSign")) {
			chapterSign = value;
		}

		if (key.equalsIgnoreCase("VerseSign")) {
			verseSign = value;
		}

		if (key.equalsIgnoreCase("ModulePrefix")) {
//		TODO	modulePrefix = value.toLowerCase();
		}
	}

	public static void parseBooks(String wPath) throws SAXException,
			IOException {
		NodeList nl = rootElement.getElementsByTagName("book");
		for (int i = 0; i < nl.getLength(); i++) {
			Element book = (Element) nl.item(i);
			String filename = book.getAttribute("path");
			parseBook(book, wPath + "/" + filename);
			// break;
		}
	}

	public static void parseBook(Element book, String fileName)
			throws SAXException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(
				new FileInputStream(new File(fileName)), encoding));
		String line;
		Element chapter = null;
		Element verse;
		int vCounter = 0;
		int cCounter = 0;
		while ((line = br.readLine()) != null) {
			if (line.contains(chapterSign)) {
				vCounter = 0;
				cCounter++;
				chapter = dom.createElement("chapter");
				book.appendChild(chapter);
				line = line.substring(line.indexOf(chapterSign));
//				ch = ch.substring(0, ch.indexOf("<"));
				line = line.replaceAll("<.*?>", "");
				chapter.setAttribute("name", line);
				chapter.setAttribute("id", book.getAttribute("id") + "."
						+ String.valueOf(cCounter));
				continue;
			}
			
			if (line.startsWith(verseSign)) {
				if (chapter == null)
					continue;
				vCounter++;
				verse = dom.createElement("v");
				chapter.appendChild(verse);
				// line = line.replaceAll("<p>[ ]*[0-9]{1,3}[ ]*", "");
				line = line.replaceAll("<.*?>", "");
				line = line.replaceAll("^\\s*\\d+\\s*", "");
				 if (removeStrong) line = line.replaceAll("\\s*\\d+", "");
				line = line.trim();
				verse.appendChild(dom.createTextNode(line));
				verse.setAttribute("id", getVerseId(verse, cCounter, vCounter));
				verse.setAttribute("lid", getLocalVerseId(verse, cCounter,
						vCounter));
			}
		}
	}

	public static void initDocument() throws IOException {
		bookCounter = 0;
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		try {
			// get an instance of builder
			DocumentBuilder db = dbf.newDocumentBuilder();

			// create an instance of DOM
			dom = db.newDocument();

		} catch (ParserConfigurationException pce) {
			// dump it
			System.out
					.println("Error while trying to instantiate DocumentBuilder "
							+ pce);
			System.exit(1);
		}

		rootElement = dom.createElement("module");
		rootElement.setAttribute("name", "<unkown>");
		rootElement.setAttribute("internationalName", "<unkown>");
		rootElement.setAttribute("shortName", "<unkown>");
		rootElement.setAttribute("id", "<unknown>");
		rootElement.setAttribute("language", "un");
		rootElement.setAttribute("category", "<unknown>");
		
		dom.appendChild(rootElement);
	}
}
